/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";

import { loadDataset, txtToMatrix , mdRender} from "../../../common/Common";
import MatView2D, {createRandomPointStyleDict} from "../../../visual/MatView2D";
import Mat from "../../../common/Mat";

import 'katex/dist/katex.min.css';
import TDom from "../../../common/TDom";
import TCommon from "../../../common/TCommon";
import { showMathStringInTable } from "../../../visual/Table2D";


class ForwardStagewiseAlg extends AlgorithmBase{
  constructor(){
    super('ForwardStagewiseAlg');

    this.descriptionUrl = `${this.homepage}/data/slm/ch8/8.2/README.md`;
    this.descriptionUrl_2 = `${this.homepage}/data/slm/ch8/8.2/README2.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch8/8.2/dataset.txt`;
    this.x = [];
    this.y = [];
  }

  init(){ 
    this.reset();

    this.initTrainDS();
  }

  reset(){
    this.dataset = undefined;
    this.trainDS = undefined;

    this.clear();

    this.initDescription();
    this.initTrainDS();
    this.updateModel();
  }

  // 模型UI
  initModelUI(){
    this.modelDiv = this.addDiv(this.domElementList.model);
  }

  // 渲染模型到页面
  updateModel(x, y){

    TDom.getDataFromUrl(this.descriptionUrl_2, (txt)=>{
      mdRender(txt, this.modelDiv);
    })

    if(x === undefined) x = [0];
    if(y === undefined) y = [1];
 
    var input_log = []
    input_log.push([`序号:`])
    for(var i=0;i<x.length;i++){
      input_log[0].push(`${i}`)
    }

    input_log.push([`x:`])
    for(var i=0;i<x.length;i++){
      input_log[1].push(`${x[i]}`)
    }

    input_log.push([`y:`])
    for(var i=0;i<x.length;i++){
      input_log[2].push(`${y[i]}`)
    }

    if(this.inputDiv) this.inputDiv.innerHTML = '';
    showMathStringInTable(this.inputDiv, input_log);
    
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    // this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    TDom.createButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    TDom.getDataFromUrl(this.descriptionUrl, (txt)=>{
      mdRender(txt, this.descriptionDiv);
    })

  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    TDom.getDataFromUrl(url, 
      (txt)=>{
      this.dataset = txtToMatrix(txt);
      this.trainDS = TCommon.loadDataset(this.dataset);
      let X = new Mat.Mat(this.trainDS.Mat);
      let y = new Mat.Mat(this.trainDS.Labels);
      for(var i=0;i<y.length;i++){
        y[i]=y[i][0]
      }
      var x=[]
      for(var i=0;i<X.length;i++){
        x.push(X[i][0])
      }
      this.pointStyleDict = createRandomPointStyleDict(this.trainDS.Labels);
      this.updateModel(x,y);
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

  error_rata(x,y,w,index=0){
    var error = 0
    if(y[index]==1){
      for(var i=0;i<=index;i++){
        if(y[i]==-1) error+=w[i]
      }
      for(var j=index+1;j<x.length;j++){
        if(y[j]==1) error+=w[j]
      }
    }
    else{
      for(var i=0;i<=index;i++){
        if(y[i]==1) error+=w[i]
      }
      for(var j=index+1;j<x.length;j++){
        if(y[j]==-1) error+=w[j]
      }
    }
  
    return error
  }
  
  error_index(x,y,w){
    var error_min = Infinity
    var index = 0
    for(var i=0;i<x.length-1;i++){
      if(y[i]!=y[i+1]){
        var err_rata = this.error_rata(x,y,w,i)
        if(err_rata<error_min){
          error_min = err_rata
          index = i
          // console.log(x,y,w);
        }
      }
    }
  
    return {'index':index,'rate':error_min}
  }
  
  sort_x(x,y){
    var sort_index = x.length-1
    for(var i=0;i<x.length-1;i++){
      for(var j=0;j<sort_index;j++){
        if(x[j]>x[j+1]){
          var x_ = x[j]
          var y_ = y[j]
          x[j] = x[j+1]
          x[j+1] = x_
          y[j] = y[j+1]
          y[j+1] = y_
        }
      }
      sort_index--
    }
  
    return {'x':x,'y':y}
  }
  
  cal_Gm(x,y,index){
    var Gm = []
    var s = (x[index]+x[index+1])/2
  
    if(y[index]==1){
      for(var i=0;i<=x.length;i++){
        if(x[i]<s) Gm.push(1)
        else Gm.push(-1)
      }
    }
    else{
      for(var i=0;i<=x.length;i++){
        if(x[i]<s) Gm.push(-1)
        else Gm.push(1)
      }
    }
  
    return Gm
  }
  
  f_x_sign(x,y,f_x=[]){
    var res = 0
    for(var i=0;i<x.length;i++){
      var x_ = 0
      for(var j=0;j<f_x.length;j++){
        if(y[f_x[j][1]]==1){
          if(i<=f_x[j][1]) x_+=f_x[j][0]*1
          else x_+=f_x[j][0]*-1
         }
         else{
          if(i<=f_x[j][1]) x_+=f_x[j][0]*-1
          else x_+=f_x[j][0]*1
         }
      }
      x_ = Math.sign(x_)
      if(x_!=y[i]) res++ 
    }
  
    return res
  }

  // 训练
  train(){
    const trainDS = this.trainDS;
    if(!trainDS) return;

    let X = new Mat.Mat(trainDS.Mat);
    let y = new Mat.Mat(trainDS.Labels);

    for(var i=0;i<y.length;i++){
      y[i]=y[i][0]
    }

    var x=[]
    for(var i=0;i<X.length;i++){
      x.push(X[i][0])
    }

    var f_x = []
    var s = this.sort_x(x,y)
    x = s.x
    y = s.y

    var w = []
    for(var i=0;i<x.length;i++) w.push(1/x.length)

    this.trainLog = []
    var count = 0
    this.trainLog.push(["迭代次数","分类器G(x)","误差率","误差率系数","最终分类器","误分类点"])

    while(1){
      count++
      var res = this.error_index(x,y,w)
      if(res.rate==0) break

      var alpha_m = parseFloat((1/2*Math.log((1-res.rate)/res.rate)).toFixed(4))
      var Z_m = 0
      var Gm = this.cal_Gm(x,y,res.index)
      for(var i=0;i<x.length;i++) Z_m+=w[i]*Math.pow(Math.E,-alpha_m*y[i]*Gm[i])
      for(var j=0;j<x.length;j++) w[j]=parseFloat((w[j]/Z_m*Math.pow(Math.E,-alpha_m*y[j]*Gm[j])).toFixed(5))
      f_x.push([alpha_m, res.index])
      var mis_point = this.f_x_sign(x,y,f_x)

      var f_str = `${f_x[0][0]}G_${1}(x)`
      for(var k=1;k<f_x.length;k++) f_str += ` + ${f_x[k][0]}G_${k+1}(x)`

      var arv = (x[res.index]+x[res.index+1])/2
      if(y[res.index]==1) this.trainLog.push([`${count}`,`G_${count}(x) = \\begin{cases} 1,~~~~~~x<${arv} \\\\-1,~~~x>${arv} \\end{cases}`,`${res.rate.toFixed(5)}`,`${alpha_m}`,`${f_str}`,`${mis_point}`])
      else this.trainLog.push([`${count}`,`G_${count}(x) = \\begin{cases} 1,~~~~~~x>${arv}\\\\-1,~~~x<${arv} \\end{cases}`,`${res.rate.toFixed(5)}`,`${alpha_m}`,`${f_str}`,`${mis_point}`])
      
      if(mis_point==0) break
    }

    this.labelTrainTips.innerText = '训练完毕';
    if(this.trainLogDiv) this.trainLogDiv.innerHTML = '';
    showMathStringInTable(this.trainLogDiv, this.trainLog);
                
  }
}


export default ForwardStagewiseAlg;